package pabudr_se;

import java.awt.geom.Area;
import java.util.ArrayList;

public class Simulator {

    private final static boolean DEBUG = DebugStatus.DEBUG;

        //Referenz Deklaration
	final AtomPanel atomPanel;
	final ArrayList<Atom> list_atome;
        final ArrayList<Barrier> list_barrieren;
        //Referenz Deklaration Ende

        final static int SLEEPTIME = 10;

	public Simulator(AtomPanel atomPanel) {
            this.atomPanel = atomPanel;
            this.list_atome = atomPanel.getList_atome();
            this.list_barrieren = atomPanel.getList_barrieren();
	}

        /**
         * bewegt jedes Atom und führt einen Kollisions-Check aus
         */
	public final void update()
	{
            for (Atom a : list_atome) {
                a.move();
                kollision(a);
            }
	}

        /**
         * führt alle Kollisionen auf dem Atom aus
         * @param a das zu prüfende Atom
         */
        final void kollision(final Atom a)
	{
            atomkollision(a);
            barrierenkollision(a);
            wandkollision(a);
	}

        /**
         * Kollision des Atoms mit der Wand
         * @param a
         */
        final void wandkollision(final Atom a)
        {
            wandkollision(a, false);
        }

        /**
         * Kollision des Atoms mit der Wand
         * @param a das Atom
         * @param checkOnly bei true soll nur auf Kollision geprueft werden
         * @return ob eine Kollision vorlag
         */
        final boolean wandkollision(final Atom a, final boolean checkOnly)
	{
            float x = a.getfX();
            float y = a.getfY();
            float r = a.getRadius();
            boolean kollision = false;

            //Richtung umdrehen falls am Rand
            if (x < r || x + r > atomPanel.getWidth())
            {
                if (checkOnly) return true;
                kollision = true;
                a.setVx(-a.getVx());
            }

            if (y < r || y + r > atomPanel.getHeight())
            {
                if (checkOnly) return true;
                kollision = true;
                a.setVy(-a.getVy());
            }

            //Abstossen, falls an der Wand
            if (x < r)
            {
                a.x += r - x;
            }
            else if (x + r > atomPanel.getWidth())
            {
                a.x -= x + r - atomPanel.getWidth();
            }
            if (y < r)
            {
                a.y += r - y;
            }
            else if (y + r > atomPanel.getHeight())
            {
                a.y -= y + r - atomPanel.getHeight();
            }

            return kollision;
	}

        /**
         * Kollision dieses Atoms mit allen Atomen aus der Liste
         * @param a das zu pruefende Atom
         */
        final void atomkollision(final Atom a)
        {
            atomkollision(a, false);
        }

        /**
         * Kollision dieses Atoms mit allen Atomen aus der Liste
         * @param a das zu pruefende Atom
         * @param checkOnly bei true soll nur auf Kollision geprueft werden
         * @return true falls ein Stoss stattfand
         */
	final boolean atomkollision(final Atom a, final boolean checkOnly)
	{
            //lege die Werte vom Atom lokal an
            final float x = a.getfX();
            final float y = a.getfY();
            final float r = a.radius;
            float rsum; //die Summe der Radien der zu vgl Atome
            float mdist; //Abstand der Mittelpunkte
            float x2, y2; //die Koordinaten des zu vgl Atoms

            for (Atom b : list_atome)
            {
                //Abbruch, wenn das gleiche Atom erreicht ist
                if (a == b)
                {
                    break;
                }

                //Teste, ob die umschliessenden Quadrate der Atome sich schneiden
                if (Math.abs(x - (x2=b.getfX())) > (rsum=(r + b.radius)) ||
                        Math.abs(y - (y2=b.getfY())) > rsum)
                {
                    //Da sie sich nicht schneiden, kann mit dem n�chsten Atom fortgefahren werden
                    continue;
                }

                mdist = (x-x2)*(x-x2) + (y-y2)*(y-y2);
                //Abstand der Mittelpunkte <> Summe der Radien
                if ( mdist < rsum*rsum)
                {
                    if (checkOnly)
                        return true;
                    else
                    {
                        Stoss(a,b);
                        entwirre(a, b, (float)Math.sqrt(mdist), rsum);
                    }
                }

            }
            return false;
	}

        /**
         * macht einen vollelastischen Stoß mit den übergebenen Atomen
         * @param a
         * @param b
         */
        private final void Stoss(final Atom a, final Atom b) {
            final Vector2 ort_a = a.getPosiVector();
            Vector2 vel_a = a.getVelVector();

            final Vector2 ort_b = b.getPosiVector();
            Vector2 vel_b = b.getVelVector();

            //Stoss-Normale
            final Vector2 n = ort_b.sub(ort_a);
            final float nn = n.mul(n);

            //Winkel und Vektoren des ersten Atoms
            final float w1 = n.mul(vel_a) / nn;
            final Vector2 par_a = n.mul(w1);
            final Vector2 sen_a = vel_a.sub(par_a);

            //Winkel und Vektoren des zweiten Atoms
            final float w2 = n.mul(vel_b) / nn;
            final Vector2 par_b = n.mul(w2);
            final Vector2 sen_b = vel_b.sub(par_b);

            if (a.getMasse() == b.getMasse()) {
              vel_a = sen_a.add(par_b);
              vel_b = sen_b.add(par_a);

            } else {
                final Vector2 parn_a = new Vector2(
                        elastic(a.masse, b.masse, par_a.x, par_b.x),
                        elastic(a.masse, b.masse, par_a.y, par_b.y)
                        );
                final Vector2 parn_b = new Vector2(
                        elastic(b.masse, a.masse, par_b.x, par_a.x),
                        elastic(b.masse, a.masse, par_b.y, par_a.y)
                        );
                vel_a = sen_a.add(parn_a);
                vel_b = sen_b.add(parn_b);
            }

            a.setVx(vel_a.x);
            a.setVy(vel_a.y);
            b.setVx(vel_b.x);
            b.setVy(vel_b.y);
        }
        /**
         * Vollelastischer Stoss
         * @param m1 Masse des ersten Koerpers
         * @param m2 Masse des zweiten Koerpers
         * @param v1 Geschwindigkeit des ersten Koerpers
         * @param v2 Geschwindigkeit des zweiten Koerpers
         * @return End-Geschwindigkeit des ersten Koerpers
         */
        private final float elastic(final float m1,
                                    final float m2,
                                    final float v1,
                                    final float v2) {
            return 
                    ((m1 - m2)*v1 - 2*m2*v2)
                    /
                    (m1 + m2);
        }

        /**
         * entwirre diese 2 Atome, falls sie uebereinander liegen
         * @param a
         * @param b
         * @param mdist Abstand der Mittelpunkte
         * @param rsum Summe der Radien
         */
        final void entwirre(final Atom a, final Atom b, final float mdist, final float rsum) {
            if (mdist > rsum) return;

            //Abstandsvektor
            final float vx = b.getfX() - a.getfX();
            final float vy = b.getfY() - a.getfY();
            //Ueberschneidung der Atome
            final float diff = rsum - mdist;

            //Soviel muessen sie sich bewegen
            final float da = diff * a.getRadius() / rsum;
            final float db = diff * b.getRadius() / rsum;

            //das Stueckchen wegbewegen
            a.x -= da * vx / rsum;
            a.y -= da * vy / rsum;

            b.x += db * vx / rsum;
            b.y += db * vy / rsum;
        }

        /**
         * Kollision dieses Atoms mit den Barrieren
         * @param a das zu prüfende Atom
         */
        final void barrierenkollision(final Atom a)
        {
            barrierenkollision(a, false);
        }

        /**
         * Kollision dieses Atoms mit den Barrieren
         * @param a das zu prüfende Atom
         * @param checkOnly bei true soll nur auf Kollision geprüft werden
         */
        private final boolean barrierenkollision(final Atom a, final boolean checkOnly) {
            for (Barrier b : list_barrieren) {
                if (barrierAtomIntersect(a, b)) {
                    if (checkOnly) return true;
                    barrierAtomStoss(a, b);
                }
            }
            return false;
        }

        /**
         * Stoesst ein Atom an einer Barriere ab
         * @param a das Atom
         * @param b die Barriere
         */
        private final void barrierAtomStoss(final Atom a, final Barrier b)
        {
            final Vector2 n = b.normale;
            Vector2 aVector = a.getVelVector();

            final float winkel = n.mul(aVector) / b.nn;
            final Vector2 parallel = n.mul(winkel);
            final Vector2 senkrecht = aVector.sub(parallel);
            if (DEBUG)
            {
                System.out.println("aVector: " + aVector);
                System.out.println("parallel: " + parallel);
                System.out.println("senkrecht: " + senkrecht);
            }
            aVector = senkrecht.sub(parallel);
            if (DEBUG) System.out.println("aVector: " + aVector);
            a.setVx(aVector.x);
            a.setVy(aVector.y);
        }

        /**
         * prueft, ob das Atom und die Barriere sich schneiden
         * @param a das Atom
         * @param b die Barriere
         * @return true bei Schnitt
         */
        final boolean barrierAtomIntersect(final Atom a, final Barrier b)
        {
            //Abstand des Atoms zur Gerade der Barriere
            final float dist_curr =
                    a.getPosiVector().sub(b.StartEnd[0]).mul(b.normale);

            if (Math.abs(dist_curr) < a.radius)
            {
                final Vector2 b1 = b.StartEnd[0];
                final Vector2 b2 = b.StartEnd[1];
                final Vector2 av = a.getPosiVector();

                //Abstaende der karthesischen Koordinaten
                final float d1x = b1.x - av.x;
                final float d1y = b1.y - av.y;
                final float d2x = b2.x - av.x;
                final float d2y = b2.y - av.y;

                //Sind die Vorzeichen dieser Abstaende ungleich so befindet sich das Atom
                //in der Barriere
                if (b.nx != 0 && b.ny != 0)
                {
                    if (d1x * d2x < 0 && d1y * d2y < 0)
                        return true;
                }
                else
                {
                    if (b.nx == 0)
                    {
                        if (d1x * d2x < 0)
                            return true;
                    }
                    else
                    {
                        if (d1y * d2y < 0)
                            return true;
                    }
                }
            }
            return false;
        }

}
